/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package ro.sergiu.widecomponents.table.beanmodel;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.table.AbstractTableModel;
import org.apache.commons.lang3.StringUtils;

/**
 *
 * @author Toporjinschi
 * @param <T>
 */
public class BeanTableModel<T> extends AbstractTableModel {

    private class BeanStorage {

        private int width = -100;
        private String title = "";
        private Class<?> type = String.class;
        private boolean editable = true;
        private Field field = null;
        private final Method method;

        public BeanStorage(Field field, Method method, String title, Class<?> type) {
            this(field, method, title, type, true, -100);
        }

        public BeanStorage(Field field, Method method, String title, Class<?> type, boolean editable, int width) {
            this.width = width;
            this.editable = editable;
            this.field = field;
            this.method = method;
            this.title = title;
            this.type = type;
        }

    }
    private final List<BeanStorage> columns = new ArrayList<BeanStorage>();
    private final Class<?> cls;
    private final BeanTableAdapter beanTableAdapter;
    private List<?> data = new ArrayList();
    private final static String[] PREFIXES = new String[]{"get", "is", "has"};

    public BeanTableModel(DefaultBeanTableModelAdapter beanTableAdapter, List data) {
        this(beanTableAdapter);
        this.data = data;
    }

    public BeanTableModel(BeanTableAdapter beanTableAdapter) {
        super();
        this.cls = beanTableAdapter.getBeanClass();
        this.beanTableAdapter = beanTableAdapter;
        initData();
    }

    private void initData() {
        Field[] fields = this.cls.getDeclaredFields();
        for (Field field : fields) {
            if (Modifier.isStatic(field.getModifiers()) || !this.beanTableAdapter.isValidField(field)) {
                continue;
            }
            Method method = determinMethod(field);
            columns.add(new BeanStorage(field, method,
                    this.beanTableAdapter.getColumnTitle(field),
                    this.beanTableAdapter.getColumnType(field),
                    this.beanTableAdapter.isColumnEditable(field),
                    this.beanTableAdapter.getWidth(field))
            );
        }
    }

    @Override
    public final Class getColumnClass(int columnIndex) {
        return this.columns.get(columnIndex).type;
    }

    @Override
    public String getColumnName(int column) {
        return this.columns.get(column).title;
    }

    @Override
    public boolean isCellEditable(int row, int column) {
        return this.columns.get(column).editable;
    }

    @Override
    public int getColumnCount() {
        return this.columns.size();
    }

    @Override
    public int getRowCount() {
        if (this.data == null) {
            return 0;
        }
        return this.data.size();
    }

    @Override
    public Object getValueAt(int row, int column) {
        Method method = this.columns.get(column).method;
        try {
            return method.invoke(this.data.get(row));
        } catch (IllegalAccessException ex) {
            Logger.getLogger(BeanTableModel.class.getName()).log(Level.SEVERE, null, ex);
        } catch (IllegalArgumentException ex) {
            Logger.getLogger(BeanTableModel.class.getName()).log(Level.SEVERE, null, ex);
        } catch (InvocationTargetException ex) {
            Logger.getLogger(BeanTableModel.class.getName()).log(Level.SEVERE, null, ex);
        }
        return "";
    }

    private Method determinMethod(Field field) {
        String name = StringUtils.capitalize(field.getName());
        Method method = null;
        for (String pref : PREFIXES) {
            try {
                method = this.cls.getMethod(pref + name);
                break;
            } catch (NoSuchMethodException ex) {
            } catch (SecurityException ex) {
            }
        }
        return method;
    }

    public List<T> getSelectedBean(int[] rowIndex) {
        List<T> selection = new ArrayList<T>();
        for (int i : rowIndex) {
            selection.add((T) this.data.get(i));
        }
        return selection;
    }
}
